const {createApp} = Vue;
window.ai = createApp({
    beforeMount() {
        this.listenCopyCodes();
        this.initMarkdown();
        this.listenImageClick();
    },
    data() {
        return {
            module: "chat",
            isMobile: false,
            smallWindow: false,
            roleList: [],
            roleId: 1,
            loginUser: {
                username: "",
                nickname: "",
                avatar: "/app/ai/avatar/user.png",
                vip: false,
                vipExpiredAt: ""
            },
            keyword: "",
            showAddressBook: true,
            box: {
                showContextMenu: false,
                showParams: false,
                showApiKey: false,
                showSendMethod: false,
                showRoleInfo: false,
                showMore: false,
            },
            showBuyLink: false,
            api: {
              key: "",
              host: "",
              enable: false
            },
            setting: {
                defaultModels: {},
                dbEnabled: false,
                enabledAlipay: false,
                enabledWechat: false,
            },
            defaultParams: {
                model: "gpt-3.5-turbo-0613",
                maxTokens: 2000,
                temperature: 0.5,
                contextNum: 6,
            },
            roleInfo: {
                roleId: 0,
                name: "",
                desc: "",
                avatar: "",
                pinned: 0,
                lastTime: 0,
                greeting: "",
                rolePrompt: "",
                model: "gpt-3.5-turbo-0613",
                maxTokens: 2000,
                temperature: 0.5,
                contextNum: 6
            },
            contextMenu: {
                top: 0,
                left: 0,
                roleId: 0,
            },
            sendMethod: "Enter", // Ctrl-Enter
            hoverMessageId: 0,
            isSlidedIn: false,
            isSlidedOut: false,
            isCompiled: true,
            showLoading: false,
        };
    },
    mounted() {
        window.addEventListener("resize", this.checkMobile);
        window.addEventListener("resize", this.setFontSize);
        this.checkMobile();
        this.setFontSize();
        this.loadSetting();
        this.loadData();
        this.loadUserInfo();
        this.listenLink();
        this.showBuyLink = location.host === "www.workerman.net";
    },
    computed: {
        chat() {
            for (const item of this.roleList) {
                if (this.roleId === item.roleId) {
                    return item;
                }
            }
            return {};
        },
        filter() {
            return [...this.roleList.filter((item) => {
                return !item.deleted && item.name.indexOf(this.keyword) !== -1;
            })].sort((a, b) => {
                if (a.pinned !== b.pinned) {
                    return b.pinned - a.pinned;
                }
                return b.lastTime - a.lastTime;
            });
        },
        showShadowLayer() {
            return Object.values(this.box).some(value => value);
        },
        isSmallWindow() {
            return this.smallWindow && !this.isMobile;
        }
    },
    methods: {
        loadData() {
            const data = JSON.parse(localStorage.getItem("ai.data") || "{}");
            ["roleId", "api", "roleList", "sendMethod", "smallWindow"].forEach((name) => {
                if (typeof data[name] !== "undefined") {
                    this[name] = data[name];
                }
            });
            this.formatRoles();
            this.loadRoles();
            this.scrollToBottom(true, false);
        },
        loadSetting() {
            $.ajax({
                url: "/app/ai/setting",
                success: (res) => {
                    if (res.code) {
                        return alert(res.msg);
                    }
                    this.setting = res.data;
                }
            });
        },
        loadUserInfo() {
            $.ajax({
                url: "/app/ai/user/info",
                success: (res) => {
                    if (res.code) {
                        return alert(res.msg);
                    }
                    this.loginUser = res.data;
                }
            });
        },
        loadRoles(reset, cb) {
            $.ajax({
                url: "/app/ai/roles.json?v=" + new Date().getTime(),
                success: (roles) => {
                    if (reset) this.roleList = [];
                    this.roleList = this.roleList.concat(roles.filter(role => !this.roleList.some(chat => chat.roleId === role.roleId)));
                    this.formatRoles();
                    this.scrollToBottom(true, false);
                    cb && cb();
                }
            });
        },
        switchModule(name) {
            this.module = name;
        },
        switchRoleId(roleId) {
            this.roleId = roleId;
            this.saveData();
            this.scrollToBottom(true, false);
            if (this.isMobile) {
                this.showAddressBook = false;
                this.isSlidedOut = false;
                this.isSlidedIn = true;
            }
        },
        sendMessage() {
            const chat = this.chat;
            if (chat.content === "" || chat.loading) {
                return;
            }
            const context = this.getContext();
            chat.messages.push({
                "id": this.genId(),
                "role": "user",
                "created": new Date().getTime(),
                "completed": true,
                "choices": [{
                    "delta": {
                        "content": chat.content
                    }
                }]
            });
            let lastId = this.genId();
            const lastMessage = {
                "id": lastId,
                "role": "assistant",
                "created": new Date().getTime(),
                "completed": false,
                "choices": [{
                    "delta": {
                        "content": this.chat.model === "dall.e" ? "生成中..." : ""
                    }
                }]
            };
            chat.messages.push(lastMessage);
            // 每个对话只保留最近40条数据
            chat.messages = chat.messages.slice(-40);
            chat.lastTime = new Date().getTime();
            this.saveData();
            chat.lastChunkIndex = 0;
            chat.loading = true;
            this.scrollToBottom(true);
            let host = "/app/ai/message/send";
            const headers = {"Content-Type": "application/json"};
            if (this.api.enable) {
                if (this.api.host) {
                    host = this.api.host;
                }
                if (this.api.key) {
                    headers.Authorization = "Bearer " + this.api.key;
                }
            }

            let maxTokens = chat.maxTokens || this.defaultParams.maxTokens;
            let model = chat.model || this.defaultParams.model;
            if (this.getBytesLength(chat.content) > this.getModelMaxLength(model)) {
                model = "gpt-3.5-turbo-16k";
            }
            let messages = [];
            let length = 0;
            if (this.chat.rolePrompt) {
                messages.push({"role": "system", "content": this.chat.rolePrompt});
                length += this.getBytesLength(this.chat.rolePrompt);
            }
            length += this.getBytesLength(chat.content);
            const maxTokenLength = this.getModelMaxLength(model);
            maxTokens = Math.ceil(Math.min(maxTokens, (maxTokenLength - length) / 1.5 - 10));
            const availableLength = maxTokenLength - maxTokens * 1.5;
            let tmp = [];
            for (let item of context.reverse()) {
                length += this.getBytesLength(item.content);
                if (length >= availableLength) {
                    break;
                }
                tmp.unshift(item);
            }

            messages = messages.concat(tmp);
            messages.push({"role": "user", "content": chat.content});
            data = JSON.stringify({
                "max_tokens": maxTokens,
                "temperature": chat.temperature || this.defaultParams.temperature,
                "stream": true,
                "messages": messages,
                "model": model,
            });

            $.ajax({
                url: host,
                data: data,
                type: "POST",
                dataType: "json",
                headers: headers,
                complete: () => {
                    let message = ai.lastMessage(chat);
                    if (!chat.loading || message.id !== lastId) {
                        return;
                    }
                    chat.lastChunkIndex = 0;
                    chat.loading = false;
                    lastMessage.completed = true;
                    ai.saveData();
                },
                success: function (res) {
                    let message = ai.lastMessage(chat);
                    if (!chat.loading || message.id !== lastId) {
                        return;
                    }
                    if (res && res.error && res.error.message) {
                        ai.lastMessage(chat).choices[0].delta.content = res.error.message;
                    } else if (res && res.data && res.data[0] && res.data[0].url) {
                        ai.lastMessage(chat).choices[0].delta.content = `![](${res.data[0].url})`;
                    } else {
                        console.log(res);
                    }
                },
                xhr: this.xhrOnProgress(function (event) {
                    let message = ai.lastMessage(chat);
                    // 已经取消
                    if (!chat.loading || message.id !== lastId) {
                        return;
                    }
                    const xhr = event.target;
                    const {responseText} = xhr;
                    let thunks = responseText.substring(chat.lastChunkIndex);
                    chat.lastChunkIndex = responseText.length;
                    const arr = thunks.split("\n");
                    arr.forEach(function (chunk) {
                        if (chunk === "") return;
                        chunk = chunk.substring(6).trim();
                        if (chunk === "" || chunk === "[DONE]") return;
                        try {
                            const data = JSON.parse(chunk);
                            if (message.error) {
                                message.choices[0].delta.content += message.error.message || "";
                            } else {
                                message.choices[0].delta.content += data.choices[0].delta.content || "";
                            }
                            ai.scrollToBottom();
                        } catch (e) {
                        }
                    });
                })
            });
            chat.content = "";
        },
        deleteMessage(id) {
            this.chat.messages = this.chat.messages.filter(message => message.id !== id);
            this.saveData();
        },
        openContextMenu(roleId, event) {
            this.contextMenu.roleId = roleId;
            const windowHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
            const contextMenuHeight = 110;
            this.contextMenu.top = event.clientY > windowHeight - contextMenuHeight ? event.clientY - contextMenuHeight : event.clientY;
            this.contextMenu.left = event.clientX + 5;
            this.box.showContextMenu = true;
            event.preventDefault();
        },
        closeContextMenu() {
            this.contextMenu.roleId = 0;
            this.box.showContextMenu = false;
        },
        formatRoles() {
            for (const chat of this.roleList) {
                if (!chat.messages) {
                    chat.messages = [];
                }
                if (!chat.messages.length && chat.greeting) {
                    chat.messages = [{
                        "role": "assistant",
                        "created": new Date().getTime(),
                        "choices": [{
                            "delta": {
                                "content": chat.greeting || ""
                            }
                        }]
                    }];
                }
                chat.content = chat.content || "";
                chat.loading = false;
                chat.lastChunkIndex = 0;
                chat.lastTime = chat.lastTime || 0;
                chat.pinned = chat.pinned || 0;
                for (const message of chat.messages) {
                    message.completed = true;
                }
            }
        },
        sendMethodSelect(item) {
            this.box.showSendMethod = false;
            this.sendMethod = item;
            this.saveData();
        },
        showPanel(name) {
            this.hideAll();
            this.box["show" + name] = true;
        },
        hideAll() {
            if (this.box.showParams) {
                this.saveData();
            }
            for (let key in this.box) {
                this.box[key] = false;
            }
            this.clearRoleInfo();
            this.closeContextMenu();
        },
        showRoleInfoBox() {
            this.clearRoleInfo();
            this.box.showRoleInfo = true;
        },
        clearRoleInfo() {
            this.roleInfo.roleId = 0;
            this.roleInfo.avatar = "/app/ai/avatar/ai.png";
            this.roleInfo.name = this.roleInfo.desc = this.roleInfo.greeting = this.roleInfo.rolePrompt = "";
            this.roleInfo = {...this.roleInfo, ...this.defaultParams};
        },
        addRole() {
            this.clearRoleInfo();
            this.box.showRoleInfo = true;
        },
        editRole(roleId) {
            roleId = roleId || this.contextMenu.roleId;
            this.clearRoleInfo();
            this.roleInfo.roleId = roleId;
            for (const item of this.roleList) {
                if (item.roleId === roleId) {
                    this.roleInfo = {...item};
                    break;
                }
            }
            this.closeContextMenu();
            this.box.showRoleInfo = true;
        },
        deleteRole() {
            const roleId = this.contextMenu.roleId;
            for (const item of this.roleList) {
                if (item.roleId === roleId) {
                    item.deleted = true;
                    break;
                }
            }
            this.saveData();
            this.closeContextMenu();
            this.roleId = this.roleList[0].roleId;
        },
        pinRole() {
            const roleId = this.contextMenu.roleId;
            for (const item of this.roleList) {
                if (item.roleId === roleId) {
                    item.pinned = item.pinned ? 0 : 1;
                    break;
                }
            }
            this.saveData();
            this.closeContextMenu();
        },
        saveRole() {
            const time = new Date().getTime();
            this.roleInfo.roleId = this.roleInfo.roleId || time;
            this.roleInfo.pinned = 0;
            const index = this.roleList.findIndex(item => item.roleId === this.roleInfo.roleId);
            if (index !== -1) {
                this.roleList[index] = {
                    ...this.roleList[index],
                    ...this.roleInfo
                };
                this.hideAll();
                this.saveData();
                return;
            }
            const roleInfo = {...this.roleInfo};
            roleInfo.messages = [];
            roleInfo.deleted = false;
            roleInfo.lastTime = new Date().getTime();
            this.roleList.push(roleInfo);
            this.formatRoles();
            this.saveData();
            this.hideAll();
        },
        saveData(key, value) {
            if (key) {
                this[key] = value;
            }
            localStorage.setItem("ai.data", JSON.stringify({
                roleId: this.roleId,
                api: this.api,
                roleList: this.roleList,
                sendMethod: this.sendMethod,
                smallWindow: this.smallWindow,
            }));
        },
        uploadAvatar() {
            const that = this;
            const formdata = new FormData();
            formdata.append("avatar", $("#avatar")[0].files[0]);
            $.ajax({
                url: "/app/ai/upload/avatar",
                type: "post",
                contentType: false,
                processData: false,
                data: formdata,
                success: function (res) {
                    that.roleInfo.avatar = res.data.url;
                }
            });
        },
        confetti() {
            function randomInRange(min, max) {
                return Math.random() * (max - min) + min;
            }

            for (let i = 0; i < 8; i++) {
                confetti({
                    startVelocity: 30,
                    particleCount: 4,
                    spread: 360,
                    ticks: 60,
                    origin: {x: randomInRange(0.15, 0.9), y: randomInRange(0, 0.6)}
                });
            }
        },
        cancel() {
            this.chat.loading = false;
            let message = this.lastMessage(this.chat);
            message.completed = true;
            this.saveData();
        },
        destroy() {
            this.cancel();
            this.chat.messages = [];
            this.formatRoles();
            this.confetti();
            this.saveData();
        },
        lastMessage(chat) {
            return chat.messages[chat.messages.length - 1];
        },
        resetSystem() {
            localStorage.clear();
            this.loadRoles(true, () => {
                this.confetti();
            });
            this.hideAll();
        },
        getContext() {
            let context = [];
            let contextNum = parseInt(this.chat.contextNum || this.defaultParams.contextNum);
            if (contextNum !== 0) {
                this.chat.messages.slice(-contextNum).forEach(function (message) {
                    context.push({role: message.role, content: message.choices[0].delta.content || ""});
                });
            }
            return context;
        },
        listenCopyCodes() {
            $(document).on("click", ".hljs .block-copy", function () {
                ai.copyToClipboard($(this).parent().parent().next().text());
            });
        },
        copyToClipboard(content) {
            if (navigator.clipboard) return navigator.clipboard.writeText(content);
            let input = document.createElement("textarea");
            input.setAttribute("readonly", "readonly");
            input.value = content;
            document.body.appendChild(input);
            input.select();
            if (document.execCommand("copy")) document.execCommand("copy");
            document.body.removeChild(input);
        },
        listenImageClick() {
            $(document).on("click", ".message-list .markdown-body img", function () {
                let imgUrl = $(this).attr("src");
                const imgExt = imgUrl.split(".").pop();
                if (imgExt.length <= 4) {
                    imgUrl = imgUrl.replace("-sm.", "-lg.");
                }
                $(".overlay img").attr("src", imgUrl);
                $(".img-preview").show();
            });
            $(document).on("click", ".overlay, .close", function () {
                $(".overlay").hide();
            });
        },
        initMarkdown() {
            this.md = window.markdownit().set({
                linkify: false,
                breaks: true,
                html: false,
                highlight: function (str, lang) {
                    const header = `<div class="position-relative"><div class="position-absolute d-flex justify-content-end align-items-center" style="right:-15px;top:-15px;"><span class="text-secondary">${lang}</span><span class="block-copy ml-2 iconfont iconfont-bg"></span></div></div>`;
                    if (lang && hljs.getLanguage(lang)) {
                        return "<pre class=\"hljs\">" + header + "<code>" + hljs.highlight(str, {language: lang}).value + "</code></pre>";
                    }
                    return "<pre class=\"hljs\">" + header + "<code>" + hljs.highlightAuto(str).value + "</code></pre>";
                }
            });
        },
        markdown(content) {
            return this.md.render(content);
        },
        xhrOnProgress(fun) {
            return function () {
                let xhr = $.ajaxSettings.xhr();
                if (typeof fun !== "function")
                    return xhr;
                if (fun) {
                    xhr.addEventListener("progress", fun);
                }
                return xhr;
            }
        },
        handleEnter(event) {
            if ((event.key === this.sendMethod && !event.ctrlKey) || (event.key !== this.sendMethod && event.ctrlKey)) {
                event.preventDefault();
                this.sendMessage();
            }
        },
        scrollToBottom(force, smooth) {
            const messageBox = this.$refs.messageBox;
            const behavior = smooth !== false ? "smooth" : "auto";
            if (force || messageBox.scrollHeight - messageBox.clientHeight <= messageBox.scrollTop + 100) {
                this.$nextTick(() => {
                    messageBox.scrollTo({
                        top: messageBox.scrollHeight,
                        behavior: behavior
                    });
                })
            }
        },
        scrollToTop(smooth) {
            const behavior = smooth !== false ? "smooth" : "auto";
            this.$refs.messageBox.scrollTo({
                top: 0,
                behavior: behavior
            });
        },
        genId() {
            return new Date().getTime() + String(Math.floor(Math.random() * 1000));
        },
        formatDate(timestamp) {
            if (!timestamp) return "";
            const date = new Date(timestamp);
            const now = new Date();
            // 如果是当天的，返回 小时:分钟
            if (date.toDateString() === now.toDateString()) {
                const hours = date.getHours().toString().padStart(2, "0");
                const minutes = date.getMinutes().toString().padStart(2, "0");
                return `${hours}:${minutes}`;
            }
            // 如果是昨天的，返回昨天
            const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
            if (date.toDateString() === yesterday.toDateString()) {
                return "昨天";
            }
            // 如果是前天的，返回前天
            const beforeYesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 2);
            if (date.toDateString() === beforeYesterday.toDateString()) {
                return "前天";
            }
            // 如果是今年的，返回 月-日 如 01-25
            if (date.getFullYear() === now.getFullYear()) {
                const month = (date.getMonth() + 1).toString().padStart(2, "0");
                const day = date.getDate().toString().padStart(2, "0");
                return `${month}-${day}`;
            }
            // 其它返回 年-月-日 如 2021-03-16
            const year = date.getFullYear().toString();
            const month = (date.getMonth() + 1).toString().padStart(2, "0");
            const day = date.getDate().toString().padStart(2, "0");
            return `${year}-${month}-${day}`;
        },
        checkMobile() {
            this.isMobile = window.innerWidth <= 768; // 假设小于768px的宽度为移动端
        },
        handleInputFocus() {
            // 修复苹果浏览器键盘遮挡输入框问题
            const isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
            if (!this.isMobile || !isSafari) return;
            setTimeout(() => {
                if (!this.innerHeight) {
                    this.innerHeight = window.innerHeight < 500 ? window.innerHeight : 395;
                } else {
                    this.innerHeight = window.innerHeight < 500 ? window.innerHeight : this.innerHeight;
                }
                window.scrollTo(0, document.body.scrollHeight - this.innerHeight);
            }, 100);
        },
        slideOut() {
            setTimeout(() => {
                this.showAddressBook = true;
            }, 80);
            this.isSlidedOut = true;
            this.isSlidedIn = false;
        },
        setFontSize() {
            if (!this.isMobile) {
                document.documentElement.style.fontSize = "15px";
                return;
            }
            const screenWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
            const baseWidth = 414; // 基准宽度
            const baseFontSize = 17; // 基准字体大小
            const fontSize = Math.min(screenWidth * baseFontSize / baseWidth, baseFontSize);
            document.documentElement.style.fontSize = fontSize + "px";
        },
        listenLink() {
            $(document).on("click", ".message-list a", function (e) {
                const link = $(this);
                if (link.attr('href') === '/app/ai/user') {
                    ai.switchModule('me');
                    e.preventDefault();
                    return false;
                }
                if (link.attr('href') === '/app/ai/user/vip') {
                    ai.switchModule('vip');
                    e.preventDefault();
                    return false;
                }
                link.attr("target", "_blank");
            });
        },
        getBytesLength(text) {
            this.encoder = this.encoder || new TextEncoder();
            return this.encoder.encode(text).length;
        },
        getModelMaxLength(model) {
            const base = 6000;
            const match = model.match(/-(\d+)k$/);
            if (!match) {
                return base;
            }
            return parseInt(match[1]) * base / 4;
        }
    }
}).mount("#app");